/***************************************************************************
      File Name:  FormHelpers.cs
   File Created:  Wednesday, June 13th 2007
      Copyright:  (C) 2007 by Darko Matesic
          Email:  darko.matesic@yahoo.com
 ***************************************************************************/

using System;
using System.Drawing;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace CallerID.Helpers
{
    public class FormHelpers
    {
        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool SetForegroundWindow(IntPtr hWnd);
        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow);
        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool IsIconic(IntPtr hWnd);

        private const int SW_RESTORE = 9;

        /// <summary>
        /// Shows specified form as dialog and returns dialog result.
        /// </summary>
        /// <param name="form"></param>
        /// <param name="ignoreError">If true and exception occures while running specified form, error is ignored and message is not displayed</param>
        /// <returns>Returns dialog result.</returns>
        public static DialogResult ShowForm(Form form, bool ignoreError)
        {
            DialogResult result = DialogResult.None;
            if(form != null) {
                try {
                    result = form.ShowDialog();
                } catch(Exception ex) {
                    if(ignoreError == false) MessageBoxEx.Show(ex.Message);
                } finally {
                    try { form.Dispose(); } catch { }
                }
            }
            return result;
        }

        /// <summary>
        /// Shows specified form as dialog and returns dialog result.
        /// </summary>
        /// <param name="form"></param>
        /// <returns>Returns dialog result.</returns>
        public static DialogResult ShowForm(Form form)
        {
            return ShowForm(form, false);
        }

        /// <summary>
        /// Activates specified form (brings it to the foreground).
        /// </summary>
        public static void ActivateForm(Form form)
        {
            if(form != null) {
                try {
                    if(IsIconic(form.Handle)) ShowWindowAsync(form.Handle, SW_RESTORE);
                    SetForegroundWindow(form.Handle);
                } catch { }
            }
        }

        /// <summary>
        /// Returns container currently active control (not container control)
        /// </summary>
        /// <returns>Returns null if it fails</returns>
        public static Control GetContainerActiveControl(ContainerControl containerControl)
        {
            if(containerControl == null) return null;
            Control control = containerControl.ActiveControl;
            while(control != null && control.GetType().IsSubclassOf(typeof(ContainerControl))) control = GetContainerActiveControl((ContainerControl)control);
            return control;
        }

        /// <summary>
        /// Moves focus to specified control (and selects all text if it is a text box)
        /// </summary>
        public static void SelectControl(Control control)
        {
            if(control != null) {
                try {
                    control.Focus();
                    if(control.GetType().IsSubclassOf(typeof(System.Windows.Forms.TextBoxBase))) ((TextBoxBase)control).SelectAll();
                } catch { }
            }
        }
        /// <summary>
        /// Moves focus to next control (and selects all text if it is a text box)
        /// </summary>
        /// <param name="form">Form that contains controls to inspect and move focus.</param>
        /// <param name="tabStopOnly">If true, inspect only controls with TabStop set.</param>
        public static void SelectFormNextControl(Form form, bool tabStopOnly)
        {
            Control control = GetContainerActiveControl(form);
            if(form != null && control != null) {
                try {
                    bool isTextBoxBaseSubclass = control.GetType().IsSubclassOf(typeof(System.Windows.Forms.TextBoxBase));
                    if(isTextBoxBaseSubclass && ((TextBoxBase)control).Multiline) return;
                    form.SelectNextControl(control, true, tabStopOnly, true, true);
                    //SelectControl(control.GetNextControl(control, true));
                    if(isTextBoxBaseSubclass) ((TextBoxBase)control).SelectAll();
                } catch { }
            }
        }

        /// <summary>
        /// Disables specified control (prevents user input and enables better user visibility than strictly disabled control).
        /// </summary>
        public static void DisableControl(Control control)
        {
            if(control != null) {
                control.TabStop = false;
                Type type = control.GetType();
                if(type.IsSubclassOf(typeof(System.Windows.Forms.UserControl)) || type == typeof(System.Windows.Forms.Panel)) {
                    try {
                        foreach(Control ctrl in control.Controls) DisableControl(ctrl);
                    } catch { }
                } else if(type == typeof(System.Windows.Forms.TextBox)) {
                    control.Enabled = true;
                    ((TextBox)control).ReadOnly = true;
                    if(control.BackColor == SystemColors.Control) control.BackColor = Color.LightGray;
                    /*
                                    } else if(type == typeof(Shared.Controls.TextBoxDecimal)) {
                                        control.Enabled = true;
                                        ((Shared.Controls.TextBoxDecimal)control).ReadOnly = true;
                                        if(control.BackColor == SystemColors.Control) control.BackColor = Color.LightGray;
                     */
                } else if(type == typeof(System.Windows.Forms.CheckBox)) {
                    //control.Enabled = true;
                    //((CheckBox)control).CheckedChanged += new EventHandler(DisabledControl_CheckedChanged);
                    control.Enabled = false;
                } else if(type == typeof(System.Windows.Forms.ComboBox)) {
                    control.Enabled = true;
                    ((ComboBox)control).DropDownStyle = ComboBoxStyle.Simple;
                    ((ComboBox)control).DroppedDown = false;
                    ((ComboBox)control).KeyPress += new KeyPressEventHandler(DisabledControl_KeyPress);
                    if(control.BackColor == SystemColors.Window) control.BackColor = Color.LightGray;
                    ((ComboBox)control).Size = new Size(((ComboBox)control).Width - 1, ((ComboBox)control).Height);
                } else if(type == typeof(System.Windows.Forms.DateTimePicker)) {
                    //Control.Enabled = true;
                    //((DateTimePicker)Control).KeyPress += new KeyPressEventHandler(DisabledControl_KeyPress);
                    control.Enabled = false;
                } else {
                    control.Enabled = false;
                }
            }
        }

        private static void DisabledControl_KeyPress(object sender, KeyPressEventArgs e)
        {
            e.Handled = true;
        }

        /// <summary>
        /// Adds KeyDown event listener which closes form on Escape key, 
        /// and KeyPress event which selects next form control on Enter
        /// </summary>
        public static void SetupFormKeyboardEvents(Form form, bool closeOnEscape, bool selectNextControlOnEnter)
        {
            form.KeyPreview = true;
            if(closeOnEscape) {
                form.KeyDown += new KeyEventHandler(form_KeyDown_CloseOnEscape);
                form.CancelButton = null;
            }
            if(selectNextControlOnEnter) {
                form.KeyDown += new KeyEventHandler(form_KeyDown_SelectNextControlOnEnter);
                form.AcceptButton = null;
            }
        }
        public static void RemoveFormKeyboardEvents(Form form)
        {
            form.KeyDown -= form_KeyDown_CloseOnEscape;
            form.KeyDown -= form_KeyDown_SelectNextControlOnEnter;
        }
        private static void form_KeyDown_CloseOnEscape(object sender, KeyEventArgs e)
        {
            try { if(e.KeyCode == Keys.Escape) ((Form)sender).Close(); } catch { };
        }
        private static void form_KeyDown_SelectNextControlOnEnter(object sender, KeyEventArgs e)
        {
            if(e.KeyCode == Keys.Enter) SelectFormNextControl((Form)sender, true);
        }

        /// <summary>
        /// Adds KeyPress event for filtering key press in text box and allowing only digits
        /// </summary>
        public static void SetupTextBoxKeyboardEvents(TextBoxBase textBox, TextBoxKeyboardEventType type)
        {
            switch(type) {
                case TextBoxKeyboardEventType.IntegerNumbers: textBox.KeyPress += new KeyPressEventHandler(textBox_KeyPress_IntegerNumbers); break;
                case TextBoxKeyboardEventType.IntegerNumbersWithMinus: textBox.KeyPress += new KeyPressEventHandler(textBox_KeyPress_IntegerNumbersWithMinus); break;
                case TextBoxKeyboardEventType.DecimalNumbers: textBox.KeyPress += new KeyPressEventHandler(textBox_KeyPress_DecimalNumbers); break;
                case TextBoxKeyboardEventType.DecimalNumbersWithMinus: textBox.KeyPress += new KeyPressEventHandler(textBox_KeyPress_DecimalNumbersWithMinus); break;
            }
        }
        public static void RemoveTextBoxKeyboardEvents(TextBoxBase textBox)
        {
            textBox.KeyPress -= textBox_KeyPress_IntegerNumbers;
            textBox.KeyPress -= textBox_KeyPress_DecimalNumbers;
        }

        /// <summary>
        /// Adds KeyPress event for filtering key press in text box and allowing only digits
        /// </summary>
        public static void SetupComboBoxKeyboardEvents(ComboBox comboBox, TextBoxKeyboardEventType type)
        {
            switch(type) {
                case TextBoxKeyboardEventType.IntegerNumbers: comboBox.KeyPress += new KeyPressEventHandler(textBox_KeyPress_IntegerNumbers); break;
                case TextBoxKeyboardEventType.IntegerNumbersWithMinus: comboBox.KeyPress += new KeyPressEventHandler(textBox_KeyPress_IntegerNumbersWithMinus); break;
                case TextBoxKeyboardEventType.DecimalNumbers: comboBox.KeyPress += new KeyPressEventHandler(textBox_KeyPress_DecimalNumbers); break;
                case TextBoxKeyboardEventType.DecimalNumbersWithMinus: comboBox.KeyPress += new KeyPressEventHandler(textBox_KeyPress_DecimalNumbersWithMinus); break;
            }
        }
        public static void RemoveComboBoxKeyboardEvents(ComboBox comboBox)
        {
            comboBox.KeyPress -= textBox_KeyPress_IntegerNumbers;
            comboBox.KeyPress -= textBox_KeyPress_DecimalNumbers;
        }
        private static void textBox_KeyPress_IntegerNumbers(object sender, KeyPressEventArgs e)
        {
            // Allow: ctrl+x (24), ctrl+c (3), ctrl+v (22)
            if((int)e.KeyChar == 3 || (int)e.KeyChar == 22 || (int)e.KeyChar == 24) return;
            if(!Char.IsDigit(e.KeyChar) && e.KeyChar != (char)Keys.Back) e.Handled = true;
        }
        private static void textBox_KeyPress_IntegerNumbersWithMinus(object sender, KeyPressEventArgs e)
        {
            // Allow: ctrl+x (24), ctrl+c (3), ctrl+v (22)
            if((int)e.KeyChar == 3 || (int)e.KeyChar == 22 || (int)e.KeyChar == 24) return;
            if(!Char.IsDigit(e.KeyChar) && e.KeyChar != '-' && e.KeyChar != (char)Keys.Back) e.Handled = true;
            try { if(e.KeyChar == '-' && ((TextBox)sender).SelectionStart != 0) e.Handled = true; } catch { }
        }
        private static void textBox_KeyPress_DecimalNumbers(object sender, KeyPressEventArgs e)
        {
            // Allow: ctrl+x (24), ctrl+c (3), ctrl+v (22)
            if((int)e.KeyChar == 3 || (int)e.KeyChar == 22 || (int)e.KeyChar == 24) return;
            if(e.KeyChar == ',') e.KeyChar = '.';
            if(!Char.IsDigit(e.KeyChar) && e.KeyChar != '.' && e.KeyChar != (char)Keys.Back) e.Handled = true;
            try { if(e.KeyChar == '.' && ((Control)sender).Text.Contains(".")) e.Handled = true; } catch { }
        }
        private static void textBox_KeyPress_DecimalNumbersWithMinus(object sender, KeyPressEventArgs e)
        {
            // Allow: ctrl+x (24), ctrl+c (3), ctrl+v (22)
            if((int)e.KeyChar == 3 || (int)e.KeyChar == 22 || (int)e.KeyChar == 24) return;
            if(e.KeyChar == ',') e.KeyChar = '.';
            if(!Char.IsDigit(e.KeyChar) && e.KeyChar != '-' && e.KeyChar != '.' && e.KeyChar != (char)Keys.Back) e.Handled = true;
            try { if(e.KeyChar == '.' && ((Control)sender).Text.Contains(".")) e.Handled = true; } catch { }
            try { if(e.KeyChar == '-' && ((TextBox)sender).SelectionStart != 0) e.Handled = true; } catch { }
        }

        /// <summary>
        /// Returns char key placed after ampersend in button text. 
        /// If key is not found returns Keys.None.
        /// </summary>
        public static Keys GetButtonShortcutKey(Button button)
        {
            try {
                if(button != null && button.Text != null && button.Text.Length > 0) {
                    int index = button.Text.IndexOf('&');
                    if(index >= 0 && (index + 1) < button.Text.Length) {
                        char ch = button.Text[index + 1];
                        if(ch >= 'a' && ch <= 'z') ch -= (char)32;
                        if((ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9')) return (Keys)ch;
                    }
                }
            } catch { }
            return Keys.None;
        }

        /// <summary>
        /// Sets new minimum, maximum and current value for specified progress bar.
        /// Returns true if any of these values are changed, false if none changed.
        /// </summary>
        public static bool SetupProgressBar(ProgressBar progressBar, int minimum, int maximum, int value)
        {
            bool changed = false;
            if(progressBar != null) {
                if(progressBar.Minimum != minimum) try { progressBar.Minimum = minimum; changed = true; } catch { }
                if(progressBar.Maximum != maximum) try { progressBar.Maximum = maximum; changed = true; } catch { }
                if(progressBar.Value != value) try { progressBar.Value = value; changed = true; } catch { }
            }
            return changed;
        }

        /// <summary>
        /// Sets new minimum, maximum and current value for specified progress bar.
        /// Returns true if any of these values are changed, false if none changed.
        /// </summary>
        public static bool SetupProgressBar(ToolStripProgressBar progressBar, int minimum, int maximum, int value)
        {
            bool changed = false;
            if(progressBar != null) {
                if(progressBar.Minimum != minimum) try { progressBar.Minimum = minimum; changed = true; } catch { }
                if(progressBar.Maximum != maximum) try { progressBar.Maximum = maximum; changed = true; } catch { }
                if(progressBar.Value != value) try { progressBar.Value = value; changed = true; } catch { }
            }
            return changed;
        }


        /// <summary>
        /// Combines horizontal and vertical string aligment (left,center,right) into content alignment (top left, top center, ...)
        /// </summary>
        public static ContentAlignment StringAlignmentToContentAlignment(StringAlignment horizontal, StringAlignment vertical)
        {
            switch(vertical) {
                case StringAlignment.Near:
                    switch(horizontal) {
                        case StringAlignment.Near: return ContentAlignment.TopLeft;
                        case StringAlignment.Center: return ContentAlignment.TopCenter;
                        case StringAlignment.Far: return ContentAlignment.TopRight;
                    }
                    break;
                case StringAlignment.Center:
                    switch(horizontal) {
                        case StringAlignment.Near: return ContentAlignment.MiddleLeft;
                        case StringAlignment.Center: return ContentAlignment.MiddleCenter;
                        case StringAlignment.Far: return ContentAlignment.MiddleRight;
                    }
                    break;
                case StringAlignment.Far:
                    switch(horizontal) {
                        case StringAlignment.Near: return ContentAlignment.BottomLeft;
                        case StringAlignment.Center: return ContentAlignment.BottomCenter;
                        case StringAlignment.Far: return ContentAlignment.BottomRight;
                    }
                    break;
            }
            return ContentAlignment.TopLeft;
        }

        /// <summary>
        /// Returns vertical or horizontal string alignment from content alignment
        /// if vertical is true it returns vertical string alignment, else returns horizontal string alignment
        /// </summary>
        public static StringAlignment ContentAlignmentToStringAlignment(ContentAlignment alignment, bool vertical)
        {
            if(vertical) {
                switch(alignment) {
                    case ContentAlignment.TopLeft:
                    case ContentAlignment.TopCenter:
                    case ContentAlignment.TopRight:
                        return StringAlignment.Near;
                    case ContentAlignment.MiddleLeft:
                    case ContentAlignment.MiddleCenter:
                    case ContentAlignment.MiddleRight:
                        return StringAlignment.Center;
                    case ContentAlignment.BottomLeft:
                    case ContentAlignment.BottomCenter:
                    case ContentAlignment.BottomRight:
                        return StringAlignment.Far;
                }
            } else {
                switch(alignment) {
                    case ContentAlignment.TopLeft:
                    case ContentAlignment.MiddleLeft:
                    case ContentAlignment.BottomLeft:
                        return StringAlignment.Near;
                    case ContentAlignment.TopCenter:
                    case ContentAlignment.MiddleCenter:
                    case ContentAlignment.BottomCenter:
                        return StringAlignment.Center;
                    case ContentAlignment.TopRight:
                    case ContentAlignment.MiddleRight:
                    case ContentAlignment.BottomRight:
                        return StringAlignment.Far;
                }
            }
            return StringAlignment.Near;
        }

        public static StringAlignment GetRadioButtonStringAlignment(RadioButton left, RadioButton center, RadioButton right)
        {
            if(left != null && left.Checked) return StringAlignment.Near;
            if(center != null && center.Checked) return StringAlignment.Center;
            if(right != null && right.Checked) return StringAlignment.Far;
            return StringAlignment.Near;
        }

        public static void SetRadioButtonStringAlignment(RadioButton left, RadioButton center, RadioButton right, StringAlignment alignment)
        {
            if(left != null) left.Checked = (alignment == StringAlignment.Near);
            if(center != null) center.Checked = (alignment == StringAlignment.Center);
            if(right != null) right.Checked = (alignment == StringAlignment.Far);
        }

        public static ContentAlignment GetRadioButtonContentAlignment(RadioButton horizontalLeft, RadioButton horizontalCenter, RadioButton horizontalRight, RadioButton verticalLeft, RadioButton verticalCenter, RadioButton verticalRight)
        {
            return StringAlignmentToContentAlignment(GetRadioButtonStringAlignment(horizontalLeft, horizontalCenter, horizontalRight), GetRadioButtonStringAlignment(verticalLeft, verticalCenter, verticalRight));
        }

        public static void SetRadioButtonContentAlignment(RadioButton horizontalLeft, RadioButton horizontalCenter, RadioButton horizontalRight, RadioButton verticalLeft, RadioButton verticalCenter, RadioButton verticalRight, ContentAlignment alignment)
        {
            SetRadioButtonStringAlignment(horizontalLeft, horizontalCenter, horizontalRight, ContentAlignmentToStringAlignment(alignment, false));
            SetRadioButtonStringAlignment(verticalLeft, verticalCenter, verticalRight, ContentAlignmentToStringAlignment(alignment, true));
        }

        /// <summary>
        /// Sets up handler for automatical column width update (not implemented in ListView).
        /// </summary>
        public static void SetupListViewColumnWidthHandler(ListView list)
        {
            new ListViewColumnWidthHandler(list);
        }

        #region SetupListViewColumnWidthHandler handlers

        /// <summary>
        /// Private class for handling automatical column width update.
        /// </summary>
        private class ListViewColumnWidthHandler
        {
            private class SizableColumn
            {
                public ColumnHeader columnHeader = null;
                public int originalWidth = 0;
                public double percent = 0;
                public SizableColumn(ColumnHeader columnHeader, int originalWidth, double percent)
                {
                    this.columnHeader = columnHeader;
                    this.originalWidth = originalWidth;
                    this.percent = percent;
                }
            }
            private ListView list = null;
            private List<SizableColumn> columns = new List<SizableColumn>();
            private int wait = 0;

            public ListViewColumnWidthHandler(ListView list)
            {
                this.list = list;
                if(this.list != null) {
                    RecordCurrentState();
                    this.list.ColumnWidthChanged += new ColumnWidthChangedEventHandler(list_ColumnWidthChanged);
                    this.list.SizeChanged += new EventHandler(list_SizeChanged);
                }
            }

            private void RecordCurrentState()
            {
                if(this.list != null) {
                    this.columns.Clear();
                    int width = this.list.Width;
                    foreach(ColumnHeader columnHeader in this.list.Columns) {
                        this.columns.Add(new SizableColumn(columnHeader, columnHeader.Width, (double)columnHeader.Width / (double)width));
                    }
                }
            }

            private void list_ColumnWidthChanged(object sender, ColumnWidthChangedEventArgs e)
            {
                if(wait > 0) return;
                wait++;
                RecordCurrentState();
                wait--;
            }

            private void list_SizeChanged(object sender, EventArgs e)
            {
                if(wait > 0) return;
                wait++;
                int width = this.list.Width;
                foreach(SizableColumn column in this.columns) {
                    column.columnHeader.Width = (int)((double)width * column.percent);
                }
                wait--;
            }
        }

        #endregion

        /// <summary>
        /// Adds forms event listeners to restore / save windows placement in configuration file.
        /// </summary>
        public static bool SetupWindowPlacement(Form form)
        {
            SetupWindowPlacementHandler handler = new SetupWindowPlacementHandler(form, false);
            return (handler != null);
        }

        #region SetupWindowPlacement handlers

        /// <summary>
        /// Private class for handling window placement
        /// </summary>
        private class SetupWindowPlacementHandler
        {
            private bool maximizeByDefault = false;

            public SetupWindowPlacementHandler(Form form, bool maximizeByDefault)
            {
                this.maximizeByDefault = maximizeByDefault;
                if(form != null) {
                    form.Load += new EventHandler(form_Load);
                    form.FormClosed += new FormClosedEventHandler(form_FormClosed);
                }
            }

            /// <summary>
            /// When form is loaded, form placement is restored from configuration file
            /// </summary>
            private void form_Load(object sender, EventArgs e)
            {
                Configuration cfg = Configuration.Instance;
                if(cfg != null) {
                    try {
                        Form form = sender as Form;
                        if(form != null) {
                            WindowPlacement p = cfg.GetWindowPlacement(form.Name);
                            if(p != null) {
                                if(form.FormBorderStyle == FormBorderStyle.Sizable || form.FormBorderStyle == FormBorderStyle.SizableToolWindow) {
                                    form.Width = p.Size.Width;
                                    form.Height = p.Size.Height;
                                }
                                Point location = p.Location;
                                if(location.X < 0) location.X = 0;
                                if(location.X > Screen.PrimaryScreen.Bounds.Width) location.X = Screen.PrimaryScreen.Bounds.Width - form.Width;
                                if(location.Y < 0) location.Y = 0;
                                if(location.Y > Screen.PrimaryScreen.Bounds.Height) location.Y = Screen.PrimaryScreen.Bounds.Height - form.Height;
                                form.Left = location.X;
                                form.Top = location.Y;
                                if(form.MaximizeBox && form.FormBorderStyle != FormBorderStyle.None) {
                                    form.WindowState = p.WindowState;
                                }
                            } else {
                                if(maximizeByDefault) form.WindowState = FormWindowState.Maximized;
                            }
                        }
                    } catch { }
                }
            }

            /// <summary>
            /// When form is closed, form placement is saved to configuration file
            /// </summary>
            private void form_FormClosed(object sender, FormClosedEventArgs e)
            {
                Configuration cfg = Configuration.Instance;
                if(cfg != null) {
                    try {
                        Form form = sender as Form;
                        if(form != null) {
                            if(form.WindowState == FormWindowState.Normal) {
                                cfg.SetWindowPlacement(form.Name, form.Location, form.Size, form.WindowState);
                            } else {
                                cfg.SetWindowPlacement(form.Name, form.RestoreBounds.Location, form.RestoreBounds.Size, form.WindowState);
                            }
                        }
                    } catch { }
                }
            }
        }

        #endregion


    }

    public enum TextBoxKeyboardEventType
    {
        Default = 0,
        IntegerNumbers,
        IntegerNumbersWithMinus,
        DecimalNumbers,
        DecimalNumbersWithMinus,
        Text
    }
}
